const { ccclass, property } = cc._decorator;


//观察者，监听订阅器属性的值的更新，并且将对应的UI的属性值更新
export default class UIWatcher {
    //需要修改属性对应的对象
    private _data: Object = null;
    //需要修改的属性
    private _key: string = "";
    //对应的UI
    private _target: any = null;
    //修改属性的对应的回调函数
    private _cb: Function = null;
    //需要修改的组件
    private _comp: cc.Component = null;
    //每个UI都应该有一个对应的观察者,并且在观察者创建时就初始对应的属性值
    constructor(data: Object, key: string, target: any, cb: Function, comp: cc.Component) {
        this._data = data;
        this._key = key;
        this._target = target;
        this._cb = cb;
        this._comp = comp;
        //在创建者观察者后，将这个观察者放入到订阅器存储观察者的数组中
        UIDepPool.instance.getDep(this._data, this._key).addWatcher(this);
        this.notify(this._data[this._key], this._data[this._key]);
    }

    notify(oldValue: any, newValue: any) {
        //如果有组件的话需要将组件一起传入
        let args = [];
        args.push(oldValue);
        args.push(newValue);
        args.push(this._comp);
        //调用对应的回调接口，将数据进行更新
        this._cb.apply(this._target, args);
    }
    //判断两个观察者是否相同  相同：属性所在的对象，属性，还回调函数和this的指向都需要相同
    isEquals(watcher: UIWatcher) {
        return watcher._data == this._data && watcher._key == this._key
            && watcher._cb == this._cb && watcher._target == this._target;
    }
    //如果UI销毁了，对应的观察者也需要销毁
    removeself() {
        //找到对应的订阅器，将对应的观察者从数组中移除
        UIDepPool.instance.getDep(this._data, this._key).removeWatcher(this);
    }

}



//订阅器，监听属性的修改，
class UIDep {
    //需要监听的属性的对象
    private _data: Object = null;
    get data() {
        return this._data;
    }
    //需要监听的属性
    private _key: string = "";
    get key() {
        return this._key;
    }

    constructor(data: Object, key: string) {
        this._data = data;
        this._key = key;
        this._definedProperty(this._data, this._key);
    }
    //存储Wacher的数组
    private _arrWatcher: UIWatcher[] = [];

    isContain(data: Object, key: string) {
        return this.data == data && this.key == key;
    }
    isEquals(dep: UIDep) {
        return dep.data == this.data && dep.key == this.key;
    }
    private _definedProperty(data: Object, key: string) {
        let newValue = data[key];
        let self = this;
        Object.defineProperty(data, key, {
            get() {
                return newValue;
            },
            set(value) {
                let oldValue = newValue;
                newValue = value;
                self._notify(oldValue, newValue);
                //console.log(UIDepPool.instance.getDep(self.data, self.key)._arrWatcher.length);
            }
        })
    }

    private _notify(oldValue: any, newValue: any) {
        //通知这个订阅器下面的所有观察者
        for (let watcher of this._arrWatcher) {
            watcher.notify(oldValue, newValue);
        }
    }

    addWatcher(watcherIn: UIWatcher) {
        //判断这个观察者是否已经存储了
        //如果已经存储就不需要存储
        for (let watcher of this._arrWatcher) {
            if (watcher.isEquals(watcherIn)) {
                return;
            }
        }
        //如果没有存储就将这个观察者存储数组中
        this._arrWatcher.push(watcherIn);
    }

    removeWatcher(watcher: UIWatcher) {
        //找到对应的观察者，并且将其从数组中删除
        for (let i = 0; i < this._arrWatcher.length; i++) {
            if (this._arrWatcher[i].isEquals(watcher)) {
                this._arrWatcher.splice(i, 1);
                break;
            }
        }

        //如果这个订阅器中没有观察者，就像这个订阅器销毁
        if (this._arrWatcher.length <= 0) {
            //从订阅器池中将这个订阅器移除
            UIDepPool.instance.removeDep(this);
        }
    }

}


//用来存储和管理所有的订阅器
class UIDepPool {
    //使用单例，这样获取的所有订阅器就是唯一的订阅器
    private static _instance: UIDepPool = null;
    static get instance() {
        if (!this._instance) {
            this._instance = new UIDepPool();
        }
        return this._instance;
    }

    //存储所有的dep订阅器
    private _arrDep: UIDep[] = [];

    getDep(data: Object, key: string) {
        for (let dep of this._arrDep) {
            if (dep.isContain(data, key)) {
                return dep;
            }
        }
        //如果没有找到就创建一个订阅器
        let dep = new UIDep(data, key);
        //并且将这个订阅器放到订阅器数组中
        this._arrDep.push(dep);
        return dep;
    }

    removeDep(dep: UIDep) {
        for (let i = 0; i < this._arrDep.length; i++) {
            if (this._arrDep[i].isEquals(dep)) {
                this._arrDep.splice(i, 1);
                //不能因为订阅池中没有订阅器就将订阅池删除
                return;
            }
        }
    }

}






